#include <string.h>

#include "co_log.h"

#include "gap_api.h"
#include "gatt_api.h"
#include "gaf_api.h"

#include "FreeRTOS.h"
#include "timers.h"

#define APP_BLE_ADV_CHN_MAX     2

#define SP_SVC_UUID             0xFFF0
#define SP_CHAR1_TX_UUID        {0xb8, 0x5c, 0x49, 0xd2, 0x04, 0xa3, 0x40, 0x71, 0xa0, 0xb5, 0x35, 0x85, 0x3e, 0xb0, 0x83, 0x07}

enum
{
    SP_IDX_SERVICE,

    SP_IDX_CHAR1_DECLARATION,
    SP_IDX_CHAR1_VALUE,
    SP_IDX_CHAR1_CFG,
    SP_IDX_CHAR1_USER_DESCRIPTION,

    SP_IDX_NB,
};


static adv_handle adv[APP_BLE_ADV_CHN_MAX];
static uint8_t adv_data[] = {
       
	#if 0
    /* gatt service information */
    0x03,                                //length of this AD
    GAP_ADVTYPE_16BIT_MORE,              //16bit service uuid AD type
    //0xff, 0xf0,                        //value.service uuid:0xFFF0
    LO_UINT16(0XFF72),
    HI_UINT16(0XFF72),
	#endif
	
    /* local device name information */
    0x09,                                                                           //length of this AD
    GAP_ADVTYPE_LOCAL_NAME_COMPLETE,                                                //complete name AD type
    'f', 'r', 'e', 'q', 'c', 'h', 'i', 'p',    //value.local device name 
	
    0x03,   // length of this data
    GAP_ADVTYPE_APPEARANCE,
    LO_UINT16(0x03c0),
    HI_UINT16(0x03c0),	
      
};

static const uint8_t scan_rsp[] = {0x09, 0x09, 'f', 'r', 'e', 'q', 'c', 'h', 'i', 'p'};

// Simple GATT Profile Service UUID: 0xFFF0
static const uint8_t sp_svc_uuid[] = UUID16_ARR(SP_SVC_UUID);

/******************************* Characteristic 1 defination *******************************/
// Characteristic 1 UUID: 0xFFF1
// Characteristic 1 data
#define SP_CHAR1_VALUE_LEN  20
uint8_t sp_char1_value[SP_CHAR1_VALUE_LEN] = {0};
// Characteristic 1 User Description
#define SP_CHAR1_DESC_LEN   17
const uint8_t sp_char1_desc[SP_CHAR1_DESC_LEN] = "Characteristic 1";

static const struct gatt_attribute simple_profile_att_table[SP_IDX_NB] =
{
    // Simple gatt Service Declaration
    [SP_IDX_SERVICE]                        =   {
        { UUID_SIZE_2, UUID16_ARR(GATT_PRIMARY_SERVICE_UUID) },     /* UUID */
        GATT_PROP_READ,                                             /* Permissions */
        UUID_SIZE_2,                                                /* Max size of the value */     /* Service UUID size in service declaration */
        (uint8_t*)sp_svc_uuid,                                      /* Value of the attribute */    /* Service UUID value in service declaration */
    },

    // Characteristic 1 Declaration
    [SP_IDX_CHAR1_DECLARATION]          =   {
        { UUID_SIZE_2, UUID16_ARR(GATT_CHARACTER_UUID) },           /* UUID */
        GATT_PROP_READ,                                             /* Permissions */
        0,                                                          /* Max size of the value */
        NULL,                                                       /* Value of the attribute */
    },
    // Characteristic 1 Value
    [SP_IDX_CHAR1_VALUE]                =   {
        { UUID_SIZE_16, SP_CHAR1_TX_UUID },                 /* UUID */
        GATT_PROP_READ | GATT_PROP_NOTI | GATT_PROP_READ_UNAUTH,    /* Permissions */
        SP_CHAR1_VALUE_LEN,                                         /* Max size of the value */
        NULL,                                                       /* Value of the attribute */    /* Can assign a buffer here, or can be assigned in the application by user */
    },

    // Characteristic 1 client characteristic configuration
    [SP_IDX_CHAR1_CFG]                  =   {
        { UUID_SIZE_2, UUID16_ARR(GATT_CLIENT_CHAR_CFG_UUID) },     /* UUID */
        GATT_PROP_READ | GATT_PROP_WRITE_CMD,                       /* Permissions */
        2,                                                          /* Max size of the value */
        NULL,                                                       /* Value of the attribute */    /* Can assign a buffer here, or can be assigned in the application by user */
    },

    // Characteristic 1 User Description
    [SP_IDX_CHAR1_USER_DESCRIPTION]     =   {
        { UUID_SIZE_2, UUID16_ARR(GATT_CHAR_USER_DESC_UUID) },      /* UUID */
        GATT_PROP_READ,                                             /* Permissions */
        SP_CHAR1_DESC_LEN,                                          /* Max size of the value */
        (uint8_t *)sp_char1_desc,                                   /* Value of the attribute */
    },
};

static const struct gatt_uuid client_regist_uuids[] = {
    { UUID_SIZE_16, {0x00, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x02,} },
    { UUID_SIZE_16, {0x01, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x02,} },
    { UUID_SIZE_16, {0x02, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x02,} },
    { UUID_SIZE_16, {0x03, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x02,} },
};

static uint8_t client_id;
static uint8_t slave_conn_id = 0xff;
static TimerHandle_t conn_param_update_timer;

static uint16_t gap_callback(struct gap_event *event)
{
    printf("gap_callback: type = %d\r\n", event->type);

    switch(event->type) {
    case GAP_EVT_ADV_REPORT:
        break;
    case GAP_EVT_NAME_REQ:
        gap_name_req_rsp(event->param.name_req.conidx,
                            event->param.name_req.token,
                            sizeof("btdm_host"),
                            (uint8_t *)"btdm_host");
        break;
    case GAP_EVT_LINK_PARAM_REQ:
        {
            struct gap_link_param_update_rsp rsp;
            rsp.accept = true;
            rsp.conidx = event->param.link_param_update_req.conidx;
            rsp.ce_len_max = 2;
            rsp.ce_len_min = 2;
            gap_param_update_rsp(&rsp);
        }
        break;
    case GAP_EVT_MASTER_CONNECT:
        {
            struct gatt_uuid uuid;
            uuid.size = UUID_SIZE_2;
            uuid.p_uuid[0] = 0x00;
            uuid.p_uuid[1] = 0x18;
            gatt_discovery_peer_svc(client_id, event->param.connect.conidx, &uuid);

            uuid.size = UUID_SIZE_16;
            uuid.p_uuid[0] = 0x00;
            uuid.p_uuid[1] = 0xfe;
            uuid.p_uuid[2] = 0x00;
            uuid.p_uuid[3] = 0x00;
            uuid.p_uuid[4] = 0x00;
            uuid.p_uuid[5] = 0x00;
            uuid.p_uuid[6] = 0x00;
            uuid.p_uuid[7] = 0x00;
            uuid.p_uuid[8] = 0x00;
            uuid.p_uuid[9] = 0x00;
            uuid.p_uuid[10] = 0x00;
            uuid.p_uuid[11] = 0x00;
            uuid.p_uuid[12] = 0x00;
            uuid.p_uuid[13] = 0x00;
            uuid.p_uuid[14] = 0xf0;
            uuid.p_uuid[15] = 0x02;
            gatt_discovery_peer_svc(client_id, event->param.connect.conidx, &uuid);
        }
        break;
    case GAP_EVT_SLAVE_CONNECT:
        {
            slave_conn_id = event->param.connect.conidx;
            xTimerStart(conn_param_update_timer, 0);
            gap_security_req(event->param.connect.conidx);
        }
        break;
    case GAP_EVT_DISCONNECT:
        xTimerStop(conn_param_update_timer, 0);
        slave_conn_id = 0xff;
        gap_adv_start(adv[0], 0, 0);
        break;
    case GAP_EVT_SMP_TK_REQ:
        LOG_INFO("gap_callback: GAP_EVT_SMP_TK_REQ\r\n");
        break;
    case GAP_EVT_SMP_NC_REQ:
        LOG_INFO("gap_callback: GAP_EVT_SMP_NC_REQ-%d\r\n", event->param.nc_req.number);
        gap_security_nc_rsp(event->param.nc_req.conidx, true);
        break;
    case GAP_EVT_ADV_END:
        break;
    default:
        break;
    }
    
    return 0;
}

static uint16_t gatt_callback(struct gatt_msg *p_msg)
{
    switch(p_msg->msg_evt) {
    case GATT_OP_PEER_SVC_DISC_END:
        switch(p_msg->param.gatt_op_cmp.operation) {
        case GATT_OP_PEER_SVC_DISC_END:
//            LOG_INFO(NULL, "GATT_OP_PEER_SVC_DISC_END: ");
//            uint16_t length = p_msg->param.gatt_op_cmp.arg_len;
//            uint8_t *buffer = p_msg->param.gatt_op_cmp.arg;
//            for(uint8_t i=0; i<length; i++) {
//                LOG_INFO(NULL, "%02x ", buffer[i]);
//            }
//            LOG_INFO(NULL, "\r\n");
            break;
        default:
            break;
        }
        break;
    default:
        break;
    }

    return 0;
}

static void conn_param_update_func(TimerHandle_t handle)
{
    if (slave_conn_id != 0xff) {
        struct gap_link_param_update_req req;
        req.conidx = slave_conn_id;
        req.intv_max = 24;
        req.intv_min = 24;
        req.latency = 32;
        req.time_out = 500;
        gap_param_update_req(&req);
    }
}

void app_ble_add_service(void)
{
    struct gatt_service service;

    service.att_nb = SP_IDX_NB;
    service.p_att_tb = simple_profile_att_table;
    service.gatt_msg_handler = NULL;

    gatt_add_service(&service);
}

void app_ble_init(void)
{
    struct gap_security_param smp_param;
    
    gap_set_cb_func(gap_callback);
    
    smp_param.mitm = false;
    smp_param.secure_connection = true;
    smp_param.bond = true;
    smp_param.rsp_mode = ENABLE_AUTO_RSP;
    smp_param.oob_used = GAP_OOB_AUTH_DATA_NOT_PRESENT;
    smp_param.io_cap = GAP_IO_CAP_NO_INPUT_NO_OUTPUT;
    gap_security_param_init(&smp_param);
    
    app_ble_add_service();

    struct gatt_client client;
    client.p_att_tb = client_regist_uuids;
    client.att_nb = sizeof(client_regist_uuids)/sizeof(client_regist_uuids[0]);
    client.gatt_msg_handler = gatt_callback;
    client_id = gatt_add_client(&client);
    gatt_add_client(NULL);
    
    conn_param_update_timer = xTimerCreate(NULL, pdMS_TO_TICKS(3000), false, NULL, conn_param_update_func);

//    gaf_cfg();
}

void app_ble_advertising_start(uint8_t adv_chn)
{
    printf("app_ble_advertising_start: %d.\r\n", adv_chn);
    
    if (adv_chn < APP_BLE_ADV_CHN_MAX) {
        if (adv[adv_chn] == NULL) {
            struct gap_adv_param adv_param;

            adv_param.adv_mode = GAP_ADV_MODE_UNDIRECT;
            adv_param.disc_mode = GAP_ADV_DISC_MODE_GEN_DISC;
            adv_param.filt_policy = GAP_ADV_FILTER_SCAN_ANY_CON_ANY;
            adv_param.adv_chnl_map = GAP_ADV_CHAN_ALL;
            adv_param.phy_mode = GAPM_PHY_TYPE_LE_1M;
            adv_param.own_addr_type = GAP_ADDR_TYPE_STATIC;    // GAPM_GEN_RSLV_ADDR  GAPM_STATIC_ADDR
            
            adv_param.adv_intv_min = 1600;
            adv_param.adv_intv_max = 1600;            
            adv[adv_chn] = gap_adv_create();
            gap_adv_set_param(adv[adv_chn], &adv_param);
            gap_adv_set_adv_data(adv[adv_chn], adv_data, sizeof(adv_data));
            gap_adv_set_scan_rsp(adv[adv_chn], scan_rsp, sizeof(scan_rsp));
        }
        gap_adv_start(adv[adv_chn], 0, 0);
    }
}

void app_ble_advertising_stop(uint8_t adv_chn)
{
    if ((adv_chn < APP_BLE_ADV_CHN_MAX)
        && (adv[adv_chn] != NULL)) {
        gap_adv_stop(adv[adv_chn]);
    }
}

void app_ble_scan_start(void)
{
    struct gap_scan_param param;

    param.scan_type = GAP_SCAN_TYPE_GEN_DISC;
    param.own_addr_type = GAP_ADDR_TYPE_STATIC;
    param.phy_mode = GAPM_PHY_TYPE_LE_1M;
    
    param.dup_filt_pol = true;
    param.scan_intv = 32;
    param.scan_window = 20;
    param.duration = 0;

    gap_scan_start(&param);
}

void app_ble_scan_stop(void)
{
    gap_scan_stop();
}

void app_ble_conn_start(struct gap_ble_addr *addr)
{
    struct gap_conn_param conn_param;
    conn_param.peer_addr.addr_type = addr->addr_type;
    conn_param.peer_addr.addr.addr[0] = addr->addr.addr[0];
    conn_param.peer_addr.addr.addr[1] = addr->addr.addr[1];
    conn_param.peer_addr.addr.addr[2] = addr->addr.addr[2];
    conn_param.peer_addr.addr.addr[3] = addr->addr.addr[3];
    conn_param.peer_addr.addr.addr[4] = addr->addr.addr[4];
    conn_param.peer_addr.addr.addr[5] = addr->addr.addr[5];
    conn_param.own_addr_type = GAP_ADDR_TYPE_STATIC;
    conn_param.phy_mode = GAPM_PHY_TYPE_LE_1M;
    conn_param.scan_intv = 32;
    conn_param.scan_window = 32;
    conn_param.conn_intv_min = 24;
    conn_param.conn_intv_max = 24;
    conn_param.supervision_to = 500;
    conn_param.slave_latency = 0;
    conn_param.ce_len_max = 2;
    conn_param.ce_len_min = 2;
    gap_conn_start(&conn_param);
}

void app_ble_conn_stop(void)
{
    gap_conn_stop();
}
